diff options
| author | Fuwn <[email protected]> | 2026-01-24 13:09:50 +0000 |
|---|---|---|
| committer | Fuwn <[email protected]> | 2026-01-24 13:09:50 +0000 |
| commit | 396acf3bbbe00a192cb0ea0a9ccf91b1d8d2850b (patch) | |
| tree | b9df4ca6a70db45cfffbae6fdd7252e20fb8e93c /src/app/(main)/websites/[websiteId]/compare | |
| download | umami-main.tar.xz umami-main.zip | |
Created from https://vercel.com/new
Diffstat (limited to 'src/app/(main)/websites/[websiteId]/compare')
3 files changed, 203 insertions, 0 deletions
diff --git a/src/app/(main)/websites/[websiteId]/compare/ComparePage.tsx b/src/app/(main)/websites/[websiteId]/compare/ComparePage.tsx new file mode 100644 index 0000000..bca8d24 --- /dev/null +++ b/src/app/(main)/websites/[websiteId]/compare/ComparePage.tsx @@ -0,0 +1,20 @@ +'use client'; +import { Column } from '@umami/react-zen'; +import { WebsiteChart } from '@/app/(main)/websites/[websiteId]/WebsiteChart'; +import { WebsiteControls } from '@/app/(main)/websites/[websiteId]/WebsiteControls'; +import { WebsiteMetricsBar } from '@/app/(main)/websites/[websiteId]/WebsiteMetricsBar'; +import { Panel } from '@/components/common/Panel'; +import { CompareTables } from './CompareTables'; + +export function ComparePage({ websiteId }: { websiteId: string }) { + return ( + <Column gap> + <WebsiteControls websiteId={websiteId} allowCompare={true} /> + <WebsiteMetricsBar websiteId={websiteId} showChange={true} /> + <Panel minHeight="520px"> + <WebsiteChart websiteId={websiteId} compareMode={true} /> + </Panel> + <CompareTables websiteId={websiteId} /> + </Column> + ); +} diff --git a/src/app/(main)/websites/[websiteId]/compare/CompareTables.tsx b/src/app/(main)/websites/[websiteId]/compare/CompareTables.tsx new file mode 100644 index 0000000..13c0516 --- /dev/null +++ b/src/app/(main)/websites/[websiteId]/compare/CompareTables.tsx @@ -0,0 +1,171 @@ +import { Column, Grid, Heading, ListItem, Row, Select } from '@umami/react-zen'; +import { useState } from 'react'; +import { DateDisplay } from '@/components/common/DateDisplay'; +import { Panel } from '@/components/common/Panel'; +import { useDateRange, useMessages, useNavigation } from '@/components/hooks'; +import { ChangeLabel } from '@/components/metrics/ChangeLabel'; +import { MetricsTable } from '@/components/metrics/MetricsTable'; +import { formatNumber } from '@/lib/format'; + +export function CompareTables({ websiteId }: { websiteId: string }) { + const [data, setData] = useState([]); + const { dateRange, dateCompare } = useDateRange(); + const { formatMessage, labels } = useMessages(); + const { + router, + updateParams, + query: { view = 'path' }, + } = useNavigation(); + const { startDate, endDate } = dateCompare; + + const params = { + startAt: startDate.getTime(), + endAt: endDate.getTime(), + }; + + const renderPath = (view: string) => { + return updateParams({ view }); + }; + + const items = [ + { + id: 'path', + label: formatMessage(labels.path), + path: renderPath('path'), + }, + { + id: 'channel', + label: formatMessage(labels.channels), + path: renderPath('channel'), + }, + { + id: 'referrer', + label: formatMessage(labels.referrers), + path: renderPath('referrer'), + }, + { + id: 'browser', + label: formatMessage(labels.browsers), + path: renderPath('browser'), + }, + { + id: 'os', + label: formatMessage(labels.os), + path: renderPath('os'), + }, + { + id: 'device', + label: formatMessage(labels.devices), + path: renderPath('device'), + }, + { + id: 'country', + label: formatMessage(labels.countries), + path: renderPath('country'), + }, + { + id: 'region', + label: formatMessage(labels.regions), + path: renderPath('region'), + }, + { + id: 'city', + label: formatMessage(labels.cities), + path: renderPath('city'), + }, + { + id: 'language', + label: formatMessage(labels.languages), + path: renderPath('language'), + }, + { + id: 'screen', + label: formatMessage(labels.screens), + path: renderPath('screen'), + }, + { + id: 'event', + label: formatMessage(labels.events), + path: renderPath('event'), + }, + { + id: 'hostname', + label: formatMessage(labels.hostname), + path: renderPath('hostname'), + }, + { + id: 'tag', + label: formatMessage(labels.tags), + path: renderPath('tag'), + }, + ]; + + const renderChange = ({ label, count }) => { + const prev = data.find(d => d.x === label)?.y; + const value = count - prev; + const change = Math.abs(((count - prev) / prev) * 100); + + return ( + !Number.isNaN(change) && ( + <Row alignItems="center" marginRight="3"> + <ChangeLabel value={value}>{formatNumber(change)}%</ChangeLabel> + </Row> + ) + ); + }; + + const handleChange = (id: any) => { + router.push(renderPath(id)); + }; + + return ( + <> + <Row width="300px"> + <Select + items={items} + label={formatMessage(labels.compare)} + value={view} + defaultValue={view} + onChange={handleChange} + > + {items.map(({ id, label }) => ( + <ListItem key={id} id={id}> + {label} + </ListItem> + ))} + </Select> + </Row> + <Panel minHeight="300px"> + <Grid columns={{ xs: '1fr', lg: '1fr 1fr' }} gap="6" height="100%"> + <Column gap="6"> + <Row alignItems="center" justifyContent="space-between"> + <Heading size="2">{formatMessage(labels.previous)}</Heading> + <DateDisplay startDate={startDate} endDate={endDate} /> + </Row> + <MetricsTable + websiteId={websiteId} + type={view} + limit={20} + showMore={false} + params={params} + onDataLoad={setData} + /> + </Column> + <Column border="left" paddingLeft="6" gap="6"> + <Row alignItems="center" justifyContent="space-between"> + <Heading size="2"> {formatMessage(labels.current)}</Heading> + <DateDisplay startDate={dateRange.startDate} endDate={dateRange.endDate} /> + </Row> + <MetricsTable + websiteId={websiteId} + type={view} + limit={20} + showMore={false} + renderChange={renderChange} + /> + </Column> + </Grid> + </Panel> + </> + ); +} diff --git a/src/app/(main)/websites/[websiteId]/compare/page.tsx b/src/app/(main)/websites/[websiteId]/compare/page.tsx new file mode 100644 index 0000000..1b2899b --- /dev/null +++ b/src/app/(main)/websites/[websiteId]/compare/page.tsx @@ -0,0 +1,12 @@ +import type { Metadata } from 'next'; +import { ComparePage } from './ComparePage'; + +export default async function ({ params }: { params: Promise<{ websiteId: string }> }) { + const { websiteId } = await params; + + return <ComparePage websiteId={websiteId} />; +} + +export const metadata: Metadata = { + title: 'Compare', +}; |